﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Diagnostics;

namespace UDPListener
{
    public delegate void UDPEventHandler(object sender, UDPEventArgs e);

    class UDPListener
    {
        #region Member Variables
        public event UDPEventHandler MessageReceived;
        private StateObject client;
        private bool listen;
        private int listenPort;
        #endregion

        #region UDPListener Constructor
        /// <summary>
        /// A UDP Asynch Listener class.
        /// </summary>
        /// <param name="listenPort">The port to listen for UDP datagrams.</param>
        public UDPListener(int listenPort)
        {
            this.listenPort = listenPort;
            IPEndPoint e = new IPEndPoint(IPAddress.Any, this.listenPort);
            UdpClient u = new UdpClient();
                        
            u.ExclusiveAddressUse = false;
            u.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.PacketInformation, true);
            u.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
            u.Client.Bind(e);

            this.client = new StateObject(e, u);
            this.listen = false;
        }
        #endregion

        #region Public Functions
        public void Start()
        {
            this.client.u.BeginReceive(new AsyncCallback(ReceiveCallback), this.client); // wait for packet async
            this.listen = true;
        }

        public void Stop()
        {
            Byte[] msg = Encoding.ASCII.GetBytes("<END>");
            this.listen = false;
            this.client.u.Send(msg, msg.Length, new IPEndPoint(IPAddress.Loopback, this.listenPort));
        }
        #endregion

        #region Getter-Setter Methods
        public bool Running { get { return this.listen; } }
        #endregion

        #region Private-Protected Functions
        private void ReceiveCallback(IAsyncResult ar)
        {
            UdpClient uc = (UdpClient)((StateObject)(ar.AsyncState)).u;
            IPEndPoint ep = (IPEndPoint)((StateObject)(ar.AsyncState)).e;
                     
            Byte[] receiveBytes = uc.EndReceive(ar, ref ep); // get packet
            
            string receiveString = Encoding.ASCII.GetString(receiveBytes);
            string senderString = ep.Address.ToString();
           
            long timeStamp = Stopwatch.GetTimestamp();
            UDPEventArgs s = new UDPEventArgs(receiveString, senderString, timeStamp);
            OnMessage(s); // fire event

            if (this.listen)
                this.client.u.BeginReceive(new AsyncCallback(ReceiveCallback), this.client);   // listen for next packet
        }

        protected virtual void OnMessage(UDPEventArgs e)
        {
            if (MessageReceived != null)
                MessageReceived(this, e);
        }
        #endregion

        #region StateObject Class Definition
        private class StateObject
        {
            private UdpClient _u;
            private IPEndPoint _e;

            public StateObject(IPEndPoint e, UdpClient u)
            {
                this.u = u;
                this.e = e;
            }

            public UdpClient u
            {
                get { return _u; }
                set { _u = value; }
            }

            public IPEndPoint e
            {
                get { return _e; }
                set { _e = value; }
            }
        }
        #endregion
    }

    #region UDPEvent Class Definition
    public class UDPEventArgs : EventArgs
    {
        private readonly string message = "";
        private readonly string sender = "";
        private readonly long timeStamp = 0;
        // Constructor.
        public UDPEventArgs(string message, string sender = "", long timeStamp = 0) 
        { 
            this.message = message; 
            this.sender = sender; 
            this.timeStamp = timeStamp; 
        }
        // Property.
        public string Message { get { return message; } }
        public string Sender { get { return sender; } }
        public long TimeStamp { get { return timeStamp; } }
    }
    #endregion
}
